/*
 * VirtualMachine.cpp
 *
 *  Created on: Nov 10, 2010
 *      Author: dhovemey
 */

#include <cwchar>
#include <cassert>
#include <algorithm>
#include "util/String.h"
#include "util/Error.h"
#include "util/CompositeException.h"
#include "fe/Lexer2.h"
#include "fe/Parser2.h"
#include "fe/ASTNode.h"
#include "fe/ASTUtil.h"
#include "fe/SemanticAnalysisException.h"
#include "fe/SemanticAnalysisVisitor.h"
#include "runtime/Object.h"
#include "runtime/Class.h"
#include "runtime/Arena.h"
#include "runtime/ClassPath.h"
#include "runtime/SourceFile.h"
#include "runtime/VirtualMachineException.h"
#include "runtime/VirtualMachine.h"

const size_t DEFAULT_HEAP_SIZE = 64*1024;

// Important: these must be ordered to match the definitions of their
// class ids.  See Class.h.
static const std::wstring s_coreRuntimeClasses[] = {
		L"System::Boolean",
		L"System::Character",
		L"System::Integer",
		L"System::Nil",
		L"System::String",
		L"System::Symbol",
};

VirtualMachine::VirtualMachine(ClassPath *clsPathToAdopt)
	: m_clsPath(clsPathToAdopt)
	, m_arena(new Arena(this, DEFAULT_HEAP_SIZE))
	, m_clsIdToClsMap(ClassIdToClassMap::size_type(Class::NUM_CORE_CLASSIDS), 0) // reserve space for the core runtime classes
{
}

VirtualMachine::~VirtualMachine()
{
	delete m_clsPath;
	delete m_arena;
}

bool VirtualMachine::bootstrap()
{
	// bootstrap the VM by finding the core runtime classes
	for (unsigned i = 0; i < sizeof(s_coreRuntimeClasses) / sizeof(wchar_t*); i++) {
		if (!loadClass(s_coreRuntimeClasses[i])) {
			return false;
		}
	}
	return true;
}

Class *VirtualMachine::findClass(const std::wstring &clsName)
{
	ClassNameToClassMap::iterator i = m_clsNameToClassMap.find(clsName);
	if (i != m_clsNameToClassMap.end()) {
		return i->second;
	} else {
		Error::raise(new VirtualMachineException(L"Could not find class " + clsName));
		return 0;
	}
}

Class *VirtualMachine::loadClass(const std::wstring &clsName)
{
	ClassNameToClassMap::iterator i = m_clsNameToClassMap.find(clsName);
	Class *desiredClass = 0;

	if (i != m_clsNameToClassMap.end()) {
		desiredClass = i->second; // class has already been loaded
	} else {
		// Attempt to read the source file
		Reader *reader;
		std::string fileName;
		if (m_clsPath->readSourceForClass(clsName, reader, fileName)) {
			return 0;
		}
		if (!loadSourceFile(reader, fileName)) {
			return 0;
		}
		i = m_clsNameToClassMap.find(clsName);
		if (i == m_clsNameToClassMap.end()) {
			Error::raise(new VirtualMachineException(L"Could not find class " + clsName));
			return 0;
		}
		desiredClass = i->second;
	}

	return desiredClass;
}

bool VirtualMachine::loadSourceFile(Reader *reader, const std::string &fileName)
{
	// Parse the source file
	std::unique_ptr<Parser2> parser(new Parser2(new Lexer2(reader)));
	ASTNode *unitAST = parser->parseTranslationUnit();
	if (!unitAST) {
		return 0;
	}

	// Create a SourceFile object to preserve the AST
	SourceFilePtr srcFile(new SourceFile(fileName, unitAST));
	std::vector<ASTNode *> clsAndModuleList;
	ASTUtil::findChildren(unitAST, ASTUtil::IsClassOrModule(), clsAndModuleList);

	// Find all of the classes and modules defined in the source file
	for (std::vector<ASTNode *>::iterator i = clsAndModuleList.begin(); i != clsAndModuleList.end(); i++) {
		// Make sure a class of the same name isn't already defined
		std::wstring clsName = (*i)->getStringValue();
		ClassNameToClassMap::iterator j = m_clsNameToClassMap.find(clsName);
		if (j != m_clsNameToClassMap.end()) {
			Class *prevCls = (*j).second;
			SourceFile *srcFile = prevCls->getSourceFile();
			Error::raise(new VirtualMachineException(
					L"Redefinition of class/module " + clsName +
					L" (previously defined in " + String::fromUtf8(srcFile->getSourceFileName()) + L")"));
			return 0;
		}

		// Perform semantic analysis
		std::unique_ptr<SemanticAnalysisVisitor> sav(new SemanticAnalysisVisitor());
		(*i)->accept(sav.get());
		if (sav->error()) {
			Error::raise(sav->getException());
			return 0;
		}

		// Determine properties of the class or module
		unsigned flags = getClassOrModuleFlags(*i);
		unsigned numFields = ASTUtil::getNumFields(*i);

		// Determine the class id for the class or module
		ClassId clsId;
		const std::wstring *p = std::find(s_coreRuntimeClasses, s_coreRuntimeClasses + Class::NUM_CORE_CLASSIDS, clsName);
		if (p != s_coreRuntimeClasses + Class::NUM_CORE_CLASSIDS) {
			// this is one of the core runtime classes with a predefined class id
			clsId = ClassId(p - s_coreRuntimeClasses);
		} else {
			// not a core runtime class - ClassId assignment is arbitrary
			clsId = ClassId(m_clsIdToClsMap.size());
		}

		// Now we can create the Class object
		Class *cls = new Class(clsId, flags, numFields);
		cls->setAST(*i);
		cls->setSourceFile(srcFile.get());

		// Add the class to the class id -> class map
		if (clsId < m_clsIdToClsMap.size()) {
			m_clsIdToClsMap[clsId] = cls;
		} else {
			m_clsIdToClsMap.push_back(cls);
		}

		// Add the class to the class name -> class map
		m_clsNameToClassMap[clsName] = cls;
	}

	return true;
}

Class *VirtualMachine::getClassForClassId(ClassId clsId)
{
	assert(clsId < ClassId(m_clsIdToClsMap.size()));
	return m_clsIdToClsMap[clsId];
}

Object *VirtualMachine::alloc(ClassId clsId)
{
	Class *cls = getClassForClassId(clsId);
	Object *obj = m_arena->alloc(cls);
	assert(obj != 0); // FIXME
	return obj;
}

unsigned VirtualMachine::getClassOrModuleFlags(ASTNode *clsOrModule)
{
	unsigned flags = 0;

	if (clsOrModule->getType() == ASTNode::AST_MODULE) {
		flags |= Class::FLAG_MODULE;
	}

	ASTUtil::ASTNodeList vec;
	ASTUtil::findChildren(clsOrModule, ASTUtil::IsMethodOrMagicMethod(), vec);
	for (ASTUtil::ASTNodeList::iterator i = vec.begin(); i != vec.end(); i++) {
		ASTNode *method = *i;
		if (method->getType() == ASTNode::AST_MAGIC_METHOD) {
			flags |= Class::FLAG_MAGIC;
		}
	}

	return flags;
}
